﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Portal.Core;
using Portal.Core.Binding.FormBuilder;
using Portal.Core.Helper;
using Portal.Core.Utils;
using Portal.Data.Crawler;
using HtmlParser = Portal.Core.Utils.HtmlParser;

namespace Portal.Data.Entity
{
    [Table(Name = "dbo.Content"), Serializable, DataContractAttribute]
    public class Content : BaseObject
    {
        #region Ctor
        public Content()
        {
            CreatedDate = DateTime.Now;
        }
        #endregion

        #region Properties
        public int ID { get { return Content_ID; } set { Content_ID = value; } }

        [SkipBinding]
        [Column(IsPrimaryKey = true, IsDbGenerated = true)]
        public int Content_ID { get; set; }

        [Column]
        public int Book_ID { get; set; }

        [Column]
        public int Chapter { get; set; }

        [Column]
        public string Title { get; set; }

        [Column]
        public string SafeName { get; set; }
        [Column]
        public string SafeNameBook { get; set; }

        [Column, SkipBinding]
        public DateTime CreatedDate { get; set; }

        [Column]
        public string CrawlUrl { get; set; }
        [Column]
        public string CrawlName { get; set; }

        [Column]
        public bool Finish { get; set; }

        [Column]
        public bool Broken { get; set; }
        #endregion

        #region Extension Props

        /// <summary>
        /// Get url like: Cat/Title/ID.extension
        /// </summary>
        [SkipBinding]
        public string URL
        {
            get
            {
                var safetitle = SafeName ?? "";
                if (safetitle.Length > 150)
                    safetitle = safetitle.Substring(0, 150) + "..";

                return string.Format(Const.UrlDetailFormat, CoreContext.basepath, SafeNameBook, safetitle, ID, CoreContext.UrlExtension);
            }
        }
        public string GetBody()
        {
            return GetBody(Book_ID, ID);
        }
        public string GetBodyMobile()
        {
            return GetBodyMobile(Book_ID, ID);
        }
        public Book GetBook()
        {
            return Book.FindByPrimaryKey(Book_ID);
        }

        /// <summary>
        /// Không phải là trường thật trong DB, chỉ dùng để lưu trữ nội dung tạm của bài tin mới
        /// </summary>
        public string BodyTemp { get; set; }
        #endregion

        #region Methods
        public static Content FindByPrimaryKey(object objectid)
        {
            return FindByPrimaryKey<Content>(objectid);
        }
        /// <summary>
        /// Danh sach chap sap xep tang dan chapter
        /// </summary>
        public static List<Content> FindByBook(int bookId, int max = 0, bool renewCaching = false)
        {
            List<Content> results;
            //getcache  -----------------------
            var parameter = new object[] { typeof(Content).Name, "FindByBook", bookId };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                {
                    results = (List<Content>)obj;
                    return max > 0 ? results.Take(max).ToList() : results;
                }
            }

            //end get cache -------------------
            using (var context = new CmsDataContext())
            {
                results = context.Contents.Where(x => x.Book_ID == bookId).OrderBy(x => x.Chapter).ToList();
            }
            CacheHelper.Instance.Set(parameter, results);
            return max > 0 ? results.Take(max).ToList() : results;
        }
        public static List<Content> Paging(int bookId, int pagesize, int pageindex, out int count)
        {
            var list = FindByBook(bookId);
            count = list.Count;
            return list.OrderBy(x => x.Chapter).Skip(pagesize * pageindex).Take(pagesize).ToList();
        }
        public static Content FindPrevious(int bookId, int chapter)
        {
            return FindByBook(bookId).LastOrDefault(x => x.Chapter < chapter);
        }
        public static Content FindNext(int bookId, int chapter)
        {
            return FindByBook(bookId).FirstOrDefault(x => x.Chapter > chapter);
        }
        /// <summary>
        /// Lay nhung chuong moi nhat, sap xep giam dan Chapter
        /// </summary>
        public static IList FindLastByBook(int bookId, int max = 5)
        {
            var all = FindByBook(bookId);
            return all.Skip(all.Count - max).Take(max).OrderByDescending(x => x.Chapter).ToList();
        }
        public static Content FindLatest(int bookId)
        {
            return FindByBook(bookId).LastOrDefault();
        }
        public static Content FindFirst(int bookId)
        {
            return FindByBook(bookId).FirstOrDefault();
        }
        public static Content FindByChapter(int bookId, int chapter)
        {
            return FindByBook(bookId).FirstOrDefault(x => x.Chapter == chapter);
        }

        public static string FindNextChapUrl(int bookId, int currChap, out int nextId)
        {
            nextId = 0;
            var next = FindNext(bookId, currChap);
            if (next != null)
            {
                nextId = next.ID;
                return next.URL;
            }
            return "";
        }
        public static string FindPreviousChapUrl(int bookId, int currChap)
        {
            var prev = FindPrevious(bookId, currChap);
            return prev != null ? prev.URL : "";
        }
        #endregion
        /// <summary>
        /// Remove all chap + file content from chapter
        /// </summary>
        /// <param name="book"></param>
        /// <param name="chapter"></param>
        /// <param name="toChapter"> </param>
        public static void RemoveFromChapter(Book book, int chapter, int toChapter = 0)
        {
            if (toChapter > 0 && toChapter < chapter)
                return;
            var allChaps = FindByBook(book.ID);
            var chapsRemover = toChapter > 0 ?
                allChaps.Where(x => x.Chapter >= chapter && x.Chapter <= toChapter).Select(x => x.ID).ToArray() :
                allChaps.Where(x => x.Chapter >= chapter).Select(x => x.ID).ToArray();
            if (chapsRemover.Any())
            {
                //1. delete------------------
                foreach (int id in chapsRemover)
                {
                    //remove chap file
                    var path = string.Format(FilePathFormat, book.ID, id);
                    FileHelper.Remove(path);
                    path = string.Format(FilePathFormatMobile, book.ID, id);
                    FileHelper.Remove(path);
                }
                //execute remove data
                string sql = "delete Content where Content_ID in (" + string.Join(",", chapsRemover) + ");";

                //2. resort chapter
                if (toChapter > 0)
                {
                    var highers = allChaps.Where(x => x.Chapter > toChapter);
                    sql = highers.Aggregate(sql, (current, c) => current + string.Format("UPDATE CONTENT SET Chapter=Chapter-{0} WHERE Content_ID={1};", (toChapter - chapter) + 1, c.ID));
                }

                using (var context = new CmsDataContext())
                {
                    context.ExecuteCommand(sql);
                }
                //3. update new info
                allChaps = FindByBook(book.ID, 0, true);
                var newLastChap = allChaps.LastOrDefault();
                if (newLastChap != null)
                {
                    book.ChangeLastChap(newLastChap.ID, allChaps.Count);
                }
            }
        }

        public override void BeforeSaveUpdate()
        {
            base.BeforeSaveUpdate();
            if (string.IsNullOrEmpty(SafeName))
            {
                SafeName = GetSafeName(Title);
            }
        }
        public override void Save()
        {
            base.Save();
            //increate totalchapter, set lastchapId for book
            using (var context = new CmsDataContext())
            {
                context.ExecuteCommand("update Book set LastChapId={1},TotalChap=TotalChap+1,ModifiedDate=GETDATE(),Status=1 where ID={0}", Book_ID, ID);
            }            
        }
        public void UpdateOnly()
        {
            using (var context = new CmsDataContext())
            {
                context.GetTable(GetType()).Attach(this);
                context.Refresh(RefreshMode.KeepCurrentValues, this);
                context.SubmitChanges();
            }
            ReCache(this);
        }
        public override void AfterSaveUpdate()
        {
            base.AfterSaveUpdate();

            //luu noi dung
            SetBody(Book_ID, Content_ID, BodyTemp);
            var bodyMobile = ConvertToMobileBody(BodyTemp);
            SetBodyMobile(Book_ID, Content_ID, bodyMobile);

            ReCache(this);
        }
        public override void AfterDelete()
        {
            base.AfterDelete();
            var book = GetBook();
            if (book != null)
            {
                int newLastChapId = 0;
                int newTotal = (book.TotalChap > 0) ? book.TotalChap - 1 : 0;
                if (book.LastChapId == ID)
                {
                    var prev = FindPrevious(Book_ID, Chapter);
                    if (prev != null)
                        newLastChapId = prev.ID;
                }
                book.ChangeLastChap(newLastChapId, newTotal);
            }
            ReCache(this);
        }

        public void ReCache(Content obj)
        {
            FindByPrimaryKey<Content>(obj.ID, true);
            FindByBook(obj.Book_ID, 0, true);

            //clear book static
            var keyUniq = Process.GetUniqueUrl(obj.URL);
            CacheHelper.Instance.Set(keyUniq, null);
        }
        public static string ConvertToMobileBody(string body, int mobileWidth = 480)
        {
            return "";
//            var newbody = body;
//            var listImgSrc = GetImageSources(newbody);
//            foreach (string s in listImgSrc)
//            {
//                //check if exist 
//                var mobipath = Book.RenderImage(s, mobileWidth);
//                if (string.IsNullOrEmpty(mobipath))
//                {
//                    mobipath = Book.RenderImage(s, mobileWidth);
//                }
//                if (mobipath.Length > 0 && mobipath != s)
//                {
//                    var i = newbody.IndexOf(s);
//                    while (i >= 0)
//                    {
//                        newbody = newbody.Substring(0, i) + mobipath + newbody.Substring(i + s.Length);
//                        i = newbody.IndexOf(s, i + mobipath.Length - 1);
//                    }
//                }
//            }
//            return newbody;
        }
        static ArrayList GetImageSources(string sContent)
        {
            var list = new ArrayList();
            var parser = new HtmlParser();
            sContent = parser.Replace(sContent, HtmlParser.Patterns.BREAK, "");
            var imgs = parser.GetContentInNode(sContent, "<img ", ">");
            foreach (var img in imgs)
            {
                var srcs = parser.GetProperty(img, "src");
                if (srcs.Count > 0)
                {
                    var s = srcs[0].Groups[4].Value.Trim();
                    var icheck = s.IndexOf("<");
                    if (icheck > 0)
                    {
                        s = s.Substring(0, icheck);
                    }
                    if (s.Length > 0)
                        list.Add(s);
                }
            }
            return list;
        }

        #region Process Body
        const Int32 BufferSize = 128;

        public const string FolderPathFormat = "/HtmlCache/ContentView/Book{0}/";
        /// <summary>
        /// "/HtmlCache/ContentView/Book{0}/Content{1}.html"
        /// </summary>
        public const string FilePathFormat = "/HtmlCache/ContentView/Book{0}/Content{1}.html";
        /// <summary>
        /// "/HtmlCache/ContentView/Book{0}/Content_m{1}.html"
        /// </summary>
        public const string FilePathFormatMobile = "/HtmlCache/ContentView/Book{0}/Content_m{1}.html";

        const string KeyBody = "ContentBody";
        const string KeyBodyMobile = "ContentBodyM";

        public static string GetBody(int bId, int cId)
        {
            //            //1: get from cached
            //            var fileName = KeyBody + cId;
            //            var obj = CacheHelper.Instance.Get(fileName);
            //            if (obj != null)
            //                return obj.ToString();

            //2: get from file
            var path = FileHelper.GetPhysicalPath(string.Format(FilePathFormat, bId, cId));
            if (File.Exists(path))
            {
                using (var fileStream = File.OpenRead(path))
                {
                    using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
                    {
                        var result = streamReader.ReadToEnd();
                        if (result.Length > 0)
                        {
                            //                            CacheHelper.Instance.Set(fileName, result);
                            return result;
                        }
                    }
                }
            }
            return "";
        }
        /// <summary>
        /// Write data to file.
        /// </summary>
        public static void SetBody(int bookId, int contentId, string body, bool withSetCache = true)
        {
            if (!string.IsNullOrEmpty(body))
            {
                //                if (withSetCache)
                //                {
                //                    var fileName = KeyBody + contentId;
                //                    CacheHelper.Instance.Set(fileName, body);
                //                }
                //write to file                
                var path = FileHelper.GetPhysicalPath(string.Format(FilePathFormat, bookId, contentId));
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(Path.GetDirectoryName(path));
                using (var w = new StreamWriter(path, false))
                {
                    w.WriteLine(body);
                }
            }
        }

        public static string GetBodyMobile(int bId, int cId)
        {
            //            //1: get from cached
            //            var fileName = KeyBodyMobile + cId;
            //            var obj = CacheHelper.Instance.Get(fileName);
            //            if (obj != null)
            //                return obj.ToString();

            //2: get from file
            var path = FileHelper.GetPhysicalPath(string.Format(FilePathFormatMobile, bId, cId));
            if (File.Exists(path))
            {
                using (var fileStream = File.OpenRead(path))
                {
                    using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
                    {
                        var result = streamReader.ReadToEnd();
                        if (result.Length > 0)
                        {
                            //CacheHelper.Instance.Set(fileName, result);
                            return result;
                        }
                    }
                }
            }
            return "";
        }
        /// <summary>
        /// Write data to file.
        /// </summary>
        public static void SetBodyMobile(int bookId, int contentId, string body, bool withSetCache = true)
        {
//            if (!string.IsNullOrEmpty(body))
//            {
//                //write to file
//                var path = FileHelper.GetPhysicalPath(string.Format(FilePathFormatMobile, bookId, contentId));
//                if (!Directory.Exists(path))
//                    Directory.CreateDirectory(Path.GetDirectoryName(path));
//                using (var w = new StreamWriter(path, false))
//                {
//                    w.WriteLine(body);
//                }
//            }
        }
        #endregion

        #region Crawler manualy
        public static string ReCrawler(int contentId)
        {
            var c = FindByPrimaryKey(contentId);
            return c != null ? Crawler(c.CrawlUrl) : "";
        }
        public static string Crawler(string url)
        {
            if (string.IsNullOrEmpty(url)) return "";
            var ccl = new CommonCrawler();
            string errMessage;
            return ccl.GetChapBody(url, out errMessage);
        }

        /// <summary>
        /// Tu dong cap nhat chuong moi theo tung host
        /// </summary>
        public static int AutoCrawler(Book b, out string errorMessage)
        {
            if (b.Finish)
            {
                errorMessage = "Truyện đã kết thúc!";
                return 0;
            }
            var host = (b.CrawlHost ?? "").ToLower();
            var resultCount = 0;
            if (host.IndexOf("blogtruyen") >= 0)
            {
                resultCount = BlogTruyen.Instance.GetNewChaps(b, out errorMessage);
            }
            else if (host.IndexOf("vitaku") >= 0)
            {
                resultCount = Vitaku.Instance.GetNewChaps(b, out errorMessage);
            }
            else if (host.IndexOf("comicvn") >= 0)
            {
                resultCount = ComicVn.Instance.GetNewChaps(b, out errorMessage);
            }
            else errorMessage = "Không cho phép crawl domain này!";

            if (resultCount > 0)
                FindByBook(b.ID, 0, true);

            return resultCount;
        }
        #endregion
    }
}